/*******************************************************************
*
*    DESCRIPTION:
*
*    AUTHOR:
*
*    HISTORY:
*
*    DATE:8/22/2006
*
*******************************************************************/
#include "config.h"
#include "outputmonitor.h"
#include "head.h"
#include "iptables.h"
#include "rulescleaner.h"
#define BUFSIZE 2048
struct ipq_handle *h;
/*
 *Function Name:CreatePromoptDialog
 *
 *Parameters:apppath--full path of app
 *
 *Description:
 *
 *Returns:
 *
 */
void CreatePromoptDialog(char *appname, int pid, int dport, struct in_addr destaddr,
						 int sport, int protocol, unsigned long packetid)
{
	char name[128];
	memset(name, 0, 128);
	char cpid[10];
	memset(cpid, 0, 10);
	char cdport[10];
	memset(cdport, 0, 10);
	char csport[10];
	memset(csport, 0, 10);
	char destip[20];
	memset(destip, 0, 10);
	char cpacketid[16];
	memset(cpacketid, 0, 16);
	char cprotocol[5];
	memset(cprotocol, 0, 5);

	GtkWidget *dialog_promopt = glade_xml_get_widget(gui_xml, "dialog_promopt");
	GtkWidget *label_appname = glade_xml_get_widget(gui_xml, "label_appname");
	GtkWidget *label_apppath = glade_xml_get_widget(gui_xml, "label_apppath");
	GtkWidget *label_pid = glade_xml_get_widget(gui_xml, "label_pid");
	GtkWidget *label_dport = glade_xml_get_widget(gui_xml, "label_dport");
	GtkWidget *label_destip = glade_xml_get_widget(gui_xml, "label_destip");
	GtkWidget *label_packetid = glade_xml_get_widget(gui_xml, "label_packetid");
	GtkWidget *label_sport = glade_xml_get_widget(gui_xml, "label_sport");
	GtkWidget *label_protocol = glade_xml_get_widget(gui_xml, "label_protocol");

	if ( protocol == 17 )
	{
		sprintf(cprotocol, "udp");
	}
	else
	{
		sprintf(cprotocol, "tcp");
	}

	char *mark = strrchr(appname, '/');
	assert(mark != NULL);
	strcpy(name, mark + 1);

	sprintf(cpid, "%d", pid);
	sprintf(cdport, "%d", dport);
	sprintf(csport, "%d", sport);

	sprintf(cpacketid, "%u", packetid);
#ifdef _DEBUG
	printf("dport:%s\n", cdport);
#endif
	char *pdestip = inet_ntoa(destaddr);
	assert(pdestip != NULL);
	strcpy(destip, pdestip);

	gtk_label_set_text(GTK_LABEL(label_appname), name);
	gtk_label_set_text(GTK_LABEL(label_apppath), appname);
	gtk_label_set_text(GTK_LABEL(label_destip), destip);
	gtk_label_set_text(GTK_LABEL(label_pid), cpid);
	gtk_label_set_text(GTK_LABEL(label_dport), cdport);
	gtk_label_set_text(GTK_LABEL(label_packetid), cpacketid);
	gtk_label_set_text(GTK_LABEL(label_sport), csport);
	gtk_label_set_text(GTK_LABEL(label_protocol), cprotocol);
	gtk_widget_show_all(dialog_promopt);
}

/*
 *Function Name:strnchr
 *
 *Parameters:
 *
 *Description:find the nth ch in s
 *
 *Returns:pointer to the nth ch
 *
 */
char * strnchr(const char *s, int ch, size_t n)
{
	if ( n == 0 )
	{
		s = NULL;
	}

	while ( s != NULL && n-- != 0 )
	{
		s = strchr(s, ch);
		if ( s != NULL && *s != '\0' && n != 0 )
		{
			s++;
		}
	}
	return(char *)s;
}
/*
 *Function Name:GetPidAndAppName
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
int GetPidAndAppName(char *cinode, char *name)
{
	DIR *dirp;
	struct dirent *direntp = malloc(sizeof(struct dirent) + _POSIX_PATH_MAX);
	struct dirent *direntp1 = malloc(sizeof(struct dirent) + _POSIX_PATH_MAX);//free later
	struct dirent *res;
	struct dirent *res1;
	dirp = opendir("/proc");
	while ( (readdir_r(dirp, direntp, &res) == 0) && (res != NULL) )
	{
		if ( (direntp->d_type == 4) && ( atoi(direntp->d_name) > 0) )//is a dir
		{
			char pathfd[80];
			char cpid[8];
			memset(cpid, 0, 8);
			memset(pathfd, 0, 80);
			sprintf(pathfd, "/proc/%s/fd", direntp->d_name);
			strcpy(cpid, direntp->d_name);
			DIR *dirpfd;
			dirpfd = opendir(pathfd);
			if ( dirpfd == NULL )
			{
				continue;
			}
			else
			{
				while ( (readdir_r(dirpfd, direntp1, &res1) == 0) && (res1 != NULL) )
				{
					if ( direntp1->d_type == 10 )//symbol link file
					{
						char buf[80];
						memset(buf, 0, 80);
						char pathinode[80];
						memset(pathinode, 0, 80);
						char pathexe[256];
						memset(pathexe, 0, 256);
						sprintf(pathinode, "%s/%s", pathfd, direntp1->d_name);
						int sz = readlink(pathinode, buf, 80);
						if ( strstr(buf, cinode) != NULL )
						{
							closedir(dirp);
							closedir(dirpfd);
							int pid = atoi(cpid);
							sprintf(pathexe, "/proc/%s/exe", cpid);
							readlink(pathexe, name, 255);
							free(direntp);
							free(direntp1);
							return pid;
						}
					}
				}
				closedir(dirpfd);
			}
		}
	}
	closedir(dirp);
	free(direntp);
	free(direntp1);
	return 0;
}


/*
 *Function Name:GetAppInfo
 *
 *Parameters:protocal--1,icmp 6,tcp 17,udp
 *port--the port of app
 *name -- output the name of app
 *
 *Description:traverse the /proc fs to find out the app's info
 *which is using the specified protocol and port
 *
 *Returns:pid and appname
 *
 */
int GetAppInfo(int protocol, int port, char *name)
{
	FILE *fp;
	char buffer[256];
	int inodenum;
	char cinode[10];
	memset(cinode, 0, 10);
	if ( protocol == 6 )//if tcp
	{
		fp = fopen("/proc/net/tcp", "r");
		if ( fp != NULL )
		{
			int i = 0;
			while ( fgets(buffer, 255, fp) != NULL )
			{
				if ( i == 0 )
				{
					i++;
					continue;
				}
				char *mark = strnchr(buffer, ':', 2);
				char cport[10];
				memset(cport, 0, 10);
				strncpy(cport, mark+1, 4);
				int iport = strtol(cport, NULL, 16);
				if ( iport == port )
				{
					memset(cinode, '\0', 10);
					char *space = strchr(buffer + 91, ' ');//buffer+91 is the pointer to inode
					memset(space, '\0', 1);
					strcpy(cinode, buffer + 91);
					break;
				}
			}

			fclose(fp);
		}
		else
		{
			printf("open /proc/net/tcp error\n");
			return 0;
		}
		//get pid and appname based on cinode
		int pid = GetPidAndAppName(cinode, name);
		return pid;
	}
	else if ( protocol == 17 )//udp
	{
		fp = fopen("/proc/net/udp", "r");
		if ( fp != NULL )
		{
			int i = 0;
			while ( fgets(buffer, 255, fp) != NULL )
			{
				if ( i == 0 )
				{
					i++;
					continue;
				}
				char *mark = strnchr(buffer, ':', 2);
				char cport[10];
				memset(cport, 0, 10);
				strncpy(cport, mark+1, 4);
				int iport = strtol(cport, NULL, 16);
				if ( iport == port )
				{
					memset(cinode, '\0', 10);
					char *space = strchr(buffer + 91, ' ');//buffer+91 is the pointer to inode
					memset(space, '\0', 1);
					strcpy(cinode, buffer + 91);
					break;
				}
			}

			fclose(fp);
		}
		else
		{
			printf("open /proc/net/udp error\n");
		}
#ifdef _DEBUG
		printf("cinode:%s\n", cinode);
#endif
		//get pid and appname based on cinode
		int pid = GetPidAndAppName(cinode, name);
		return pid;
	}
	else
	{
		printf("unknow protocal:%d\n", protocol);
		return 0;
	}
}
/*
 *Function Name:IsAppInConfFile
 *
 *Parameters:
 *
 *Description:travese the app_conf array and find out if the appname is in it
 *
 *Returns:1--allowed app 0--denyed app -1--unknowed app
 *
 */
int GetAppAuthorization(char *apppath, int *index)
{
	int i;
	assert(apppath != NULL);
	char *mark = strrchr(apppath, '/');//find name
	assert(mark != NULL);
	for ( i = 0; i < app_counter; i++ )
	{
		if ( strcmp(mark + 1, app_conf[i].name) == 0 )
		{
			if ( app_conf[i].allow == 1 )
			{
				*index = i;
				return 1;//allowed app
			}
			else
			{
				return 0;//deny app
			}
		}
	}
	return -1;//unknow app
}
/*
 *Function Name:die
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
static void die(struct ipq_handle *h)
{
	ipq_perror("passer");
	ipq_destroy_handle(h);
	exit(1);
}

/*
 *Function Name:OutputMonitor
 *
 *Parameters:
 *
 *Description:
 *
 *Returns:
 *
 */
void* OutputMonitor(void *data)
{
#ifdef _DEBUG
	printf("enter monitor thread\n");
#endif
	int status;
	unsigned char buf[BUFSIZE];

	pthread_detach(pthread_self());

	h = ipq_create_handle(0, PF_INET);

	assert(h != NULL);

	status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
	if ( status < 0 )
		die(h);

	do
	{
		status = ipq_read(h, buf, BUFSIZE, 0);
		if ( status < 0 )
			die(h);

		switch ( ipq_message_type(buf) )
		{
			case NLMSG_ERROR:
				{

					fprintf(stderr, "Received error message %d\n",
							ipq_get_msgerr(buf));
					break;
				}
			case IPQM_PACKET:
				{
					ipq_packet_msg_t *m = ipq_get_packet(buf);

					struct iphdr *ip = (struct iphdr*) m->payload;

					//udp and tcp header's first 32 bit has the same meaning
					struct tcphdr *tcp = (struct tcphdr*) (m->payload + (4 * ip->ihl));

					int sport = htons(tcp->source);        
					int dport = htons(tcp->dest);
					struct in_addr destaddr;
					destaddr.s_addr = ip->daddr;
					//check FIN package
					if ( ip->protocol == 6 )//tcp
					{

						if ( tcp->fin )
						{
							//mutext
							//add sport
#ifdef _DEBUG
							printf("sport:%d dport:%d tcp link terminated fin:%d syn:%d\n", dport, sport, tcp->fin, tcp->syn);
#endif
							//we need check if it's a listening port
							pthread_mutex_lock(&mutex);
							delete_sport[delete_rules_counter] = sport;
							delete_rules_counter ++;
							if ( delete_rules_counter > MAX_SPORT )
							{
								fprintf(stderr, "MAX_SPORT is not big enough\n");
							}
							pthread_mutex_unlock(&mutex);
							ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL);
							//refresh global array
							continue;
						}
					}
					//get the pid and appname
					char apppath[1024];
					memset(apppath, 0, 1024);
					int pid = GetAppInfo(ip->protocol, sport, apppath);//we should check pid here
#ifdef _DEBUG
					printf("appname:%s pid:%d sport:%d dport:%d protocol:%d\n", apppath, pid, sport, dport, ip->protocol);
#endif	
					int index;
					int auth_type = GetAppAuthorization(apppath, &index);
					if ( auth_type == 1 )
					{
						IptablesAllowOldApp(sport, dport, ip->protocol, pid, index);
						//let package go
						ipq_set_verdict(h, m->packet_id, NF_ACCEPT, 0, NULL);
					}
					else if ( auth_type == 0 )//deny app don't do anything
					{
						ipq_set_verdict(h, m->packet_id, NF_DROP, 0, NULL);
					}
					else
					{

						//create a notfiy dialog to user for unknowed app
						CreatePromoptDialog(apppath, pid, dport, destaddr, sport, ip->protocol, m->packet_id);
					}
					break;
				}

			default:
				fprintf(stderr, "Unknown message type!\n");
				break;
		}
	} while ( 1 );

	ipq_destroy_handle(h);
}
